﻿using System;
using Tao.OpenGl;
using Tao.FreeGlut;
using System.Threading;
using MoveModel;
using MoveView.MyMath;
using MoveView.Model;

namespace MoveView
{

    class StereoRender
    {
		//flags
		private bool stereo = true;
		private bool fullscreen = true;

		private CAMERA camera;

        private int windowWidth = 1920;
        private int windowHeight = 1080;
        private int windowId = 0;

        private Move move;
        private Drawing drawing;
        private ModelViewCalc mvc;

        private int calibrationStep = 1;

        public StereoRender(bool isStereo, bool isFullscreen, int width, int height, Move move)
        {
            this.move = move;
            drawing = new Drawing();

            //temp
            drawing.StrokeHistory.Add(new StrokeDisplay());

            camera.screenheight = height;
            camera.screenwidth = width;

            stereo = isStereo;
            fullscreen = isFullscreen;

            Glut.glutInit();
            Glut.glutInitDisplayMode(Glut.GLUT_DOUBLE | Glut.GLUT_RGB | Glut.GLUT_DEPTH);
			windowId = Glut.glutCreateWindow("Move Sculpting");
			Glut.glutReshapeWindow(windowWidth,windowHeight);
			if(fullscreen)
				Glut.glutFullScreen();

           	Glut.glutDisplayFunc(Display);
			Glut.glutVisibilityFunc(HandleVisibility);
			Glut.glutReshapeFunc(HandleReshape);
            Glut.glutSetCursor(Glut.GLUT_CURSOR_NONE);
            Glut.glutSpecialFunc(HandleSpecialKeys);
            Glut.glutKeyboardFunc(HandleKeyboard);

            Gl.glShadeModel(Gl.GL_SMOOTH);
            Gl.glClearColor(0, 0, 0, 1);

			CreateEvironment();
			CameraHome();

			Glut.glutMainLoop();
        }

        private void HandleSpecialKeys(int key, int x, int y)
        {
            switch (key)
            {
                case Glut.GLUT_KEY_F1:
                    Stroke.PolygonMode = !Stroke.PolygonMode;
                    break;
                default:
                    break;
            }
        }

        private void HandleKeyboard(byte key, int x, int y)
        {
            switch (key)
            {
                case 27: //ESC
                    Glut.glutDestroyWindow(windowId);
                    break;
                case (int)'f':
                    fullscreen = !fullscreen;
                    Glut.glutFullScreen();
                    break;
                case (int)'s':
                    stereo = !stereo;
                    break;
                default:
                    break;
            }
        }
        private void CreateEvironment ()
		{
			Gl.glEnable(Gl.GL_DEPTH_TEST);
			Gl.glDisable(Gl.GL_LINE_SMOOTH);
			Gl.glDisable(Gl.GL_POINT_SMOOTH);
			Gl.glEnable(Gl.GL_POLYGON_SMOOTH);
			Gl.glShadeModel(Gl.GL_SMOOTH);
			Gl.glDisable(Gl.GL_DITHER);
			Gl.glDisable(Gl.GL_CULL_FACE);

			Gl.glLineWidth(1.0f);
			Gl.glPointSize(1.0f);

			Gl.glPolygonMode(Gl.GL_FRONT_AND_BACK, Gl.GL_FILL);
			Gl.glFrontFace(Gl.GL_CW);
			Gl.glClearColor(0.0f,0.0f,0.0f,0.0f);
			Gl.glColorMaterial(Gl.GL_FRONT_AND_BACK,Gl.GL_AMBIENT_AND_DIFFUSE);
			Gl.glEnable(Gl.GL_COLOR_MATERIAL);
			Gl.glPixelStorei(Gl.GL_UNPACK_ALIGNMENT,1);
		}
        
        private void Display()
        {
            if (calibrationStep > 0)
                HandleMoveInputCalibration();  
            else
                HandleMoveInput();

            XYZ r = new XYZ();
            double dist, ratio, radians, scale, wd2, ndfl;
            double left, right, top, bottom, near = 0.1, far = 10000;

            //Clip to avoid extreme stereo
            if (stereo)
                near = camera.focallength / 5;

            //Misc stuff
            ratio = camera.screenwidth / (double)camera.screenheight;
            radians = Constants.DTOR * camera.aperture / 2;
            wd2 = near * Math.Tan(radians);
            ndfl = near / camera.focallength;


            //Clear buffers
            Gl.glDrawBuffer(Gl.GL_BACK_LEFT);
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            

            if (stereo)
            {
                Gl.glViewport(0, 0, windowWidth / 2, windowHeight);
                Gl.glDrawBuffer (Gl.GL_BACK_RIGHT);
                Gl.glClear (Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

                //Derive two eyes positions
                r = Constants.Crossproduct(camera.vd, camera.vu);
                Constants.Normalize(ref r);
                r.x *= camera.eyesep / 2.0;
                r.y *= camera.eyesep / 2.0;
                r.z *= camera.eyesep / 2.0;

                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glLoadIdentity();
                left = -ratio * wd2 - 0.5 * camera.eyesep * ndfl;
                right = ratio * wd2 - 0.5 * camera.eyesep * ndfl;
                top = wd2;
                bottom = -wd2;
                Gl.glFrustum(left, right, bottom, top, near, far);

                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glDrawBuffer(Gl.GL_BACK_RIGHT);
                Gl.glLoadIdentity();
                
                MakeCursor();

                Glu.gluLookAt(
                        camera.cc.x + r.x, camera.cc.y + r.y, camera.cc.z + r.z,
                        camera.cc.x + r.x + camera.vd.x,
                        camera.cc.y + r.y + camera.vd.y,
                        camera.cc.z + r.z + camera.vd.z,
                        camera.vu.x, camera.vu.y, camera.vu.z
                    );
                
                mvc = new ModelViewCalc(
                    camera.cc.x + r.x, camera.cc.y + r.y, camera.cc.z + r.z,
                        camera.cc.x + r.x + camera.vd.x,
                        camera.cc.y + r.y + camera.vd.y,
                        camera.cc.z + r.z + camera.vd.z,
                        camera.vu.x, camera.vu.y, camera.vu.z
                    );
                MakeLightning(true);
                if (calibrationStep > 0)
                    calibrationDisplay();
                else
                    MakeGeometry();

                Gl.glViewport(windowWidth / 2, 0, windowWidth / 2, windowHeight);


                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glLoadIdentity();
                left = -ratio * wd2 + 0.5 * camera.eyesep * ndfl;
                right = ratio * wd2 + 0.5 * camera.eyesep * ndfl;
                top = wd2;
                bottom = -wd2;
                Gl.glFrustum(left, right, bottom, top, near, far);

                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glDrawBuffer(Gl.GL_BACK_LEFT);
                Gl.glLoadIdentity();


                MakeCursor();

                Gl.glPushMatrix();
                Glu.gluLookAt(
                    camera.cc.x - r.x, camera.cc.y - r.y, camera.cc.z - r.z,
                    camera.cc.x - r.x + camera.vd.x,
                    camera.cc.y - r.y + camera.vd.y,
                    camera.cc.z - r.z + camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                );
                mvc = new ModelViewCalc(
                    camera.cc.x - r.x, camera.cc.y - r.y, camera.cc.z - r.z,
                    camera.cc.x - r.x + camera.vd.x,
                    camera.cc.y - r.y + camera.vd.y,
                    camera.cc.z - r.z + camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                    );
                Gl.glPopMatrix();
                MakeLightning(true);
                if (calibrationStep > 0)
                    calibrationDisplay();
                else
                    MakeGeometry();

            }
            else
            {
                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glLoadIdentity();
                left = -ratio * wd2;
                right = ratio * wd2;
                top = wd2;
                bottom = -wd2;
                Gl.glFrustum(left, right, bottom, top, near, far);

                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glDrawBuffer(Gl.GL_BACK_LEFT);
                Gl.glLoadIdentity();

                Glu.gluLookAt(
                    0, 0, camera.focallength,
                    camera.vd.x,
                    camera.vd.y,
                    camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                );

                MakeCursor();

                Gl.glPushMatrix();
                Glu.gluLookAt(
                    camera.cc.x, camera.cc.y, camera.cc.z,
                    camera.vd.x,
                    camera.vd.y,
                    camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                );

                mvc = new ModelViewCalc(
                    camera.cc.x, camera.cc.y, camera.cc.z,
                    camera.vd.x,
                    camera.vd.y,
                    camera.vd.z,
                    camera.vu.x, camera.vu.y, camera.vu.z
                    );
                Gl.glPopMatrix();

                MakeLightning(true);
                if (calibrationStep > 0)
                    calibrationDisplay();
                else
                    MakeGeometry();
            }

            //Gl.glFlush(); //Not not neccesary for double buffers
            Glut.glutSwapBuffers();

        }

        void calibrationDisplay()
        {
            MakeLightning(false);
            Gl.glPushMatrix();
            Gl.glColor3d(1.0, 1.0, 1.0);
            Gl.glRasterPos2d(-55, 30);
            switch(calibrationStep)
            { 
                case 1:
                Glut.glutBitmapString(Glut.GLUT_BITMAP_TIMES_ROMAN_24, "Calibration in progress. To cancel, press circle button.\nHold controller in front of you with your maximum comfortable reach and press Move button to continue.");
                break;
                case 2:
                Glut.glutBitmapString(Glut.GLUT_BITMAP_TIMES_ROMAN_24, "Calibration in progress. To cancel, press circle button.\nHold controller in front of you with your minimun comfortable reach and press Move button to continue.");
                break;
                case 3:
                Glut.glutBitmapString(Glut.GLUT_BITMAP_TIMES_ROMAN_24, "Calibration process complete. Thank you for cooperation.");
                calibrationStep++;
                break;
                case 4:
                Thread.Sleep(1500);
                move.calibrationDone = true;
                calibrationStep = 0;
                break;
                default:
                break;
            }
            Gl.glPopMatrix();
            MakeLightning(true);
        }
        
        private void MakeCursor()
        {
            if (!holdBrush)
            {
                Gl.glPushMatrix();
                Gl.glTranslated(move.X, move.Y, move.Radius);
                if (move.Trigger > 0)
                    drawing.drawWireShape(move.Trigger / 20);
                else
                    drawing.drawWireShape(2);
                Gl.glPopMatrix();
            }
        }

        private bool holdBrush = false;
        private void MakeGeometry()
        {
            Glu.gluLookAt(camera.cc.x, camera.cc.y, camera.cc.z, 0, 0, 0, 0, 1, 0);
            //Gl.glRotated(camera.cc.x, 1, 0, 0);
            //Gl.glRotated(camera.cc.y, 0, 1, 0);
            //Gl.glRotated(camera.cc.z, 0, 0, 1);


            Glut.glutWireCube(20);
            DrawHistory();

            //linie pomocnicze
            Gl.glColor3d(1, 1, 1);
            Gl.glLineWidth(1);
            Gl.glBegin(Gl.GL_LINES);
                Gl.glVertex3d(0, -500, 0);
                Gl.glVertex3d(0, 500, 0);
                Gl.glVertex3d(0, 0, -500);
                Gl.glVertex3d(0, 0, 500);
                Gl.glVertex3d(-500, 0, 0);
                Gl.glVertex3d(500, 0, 0);
            Gl.glEnd();

        }

        private void DrawHistory()
        {

            foreach (var stroke in drawing.StrokeHistory)
            {
                stroke.DrawStroke();
            }


            //foreach (Shape s in drawing.History)
            //{
            //    Gl.glPushMatrix();

            //    Gl.glRotated(camera.cc.x + s.ox, 1, 0, 0);
            //    Gl.glRotated(camera.cc.y + s.oy, 0, 1, 0);
            //    Gl.glRotated(camera.cc.z + s.oz, 0, 0, 1);
            //    Gl.glPushMatrix();
            //    Gl.glTranslated(s.x, s.y, s.z);
            //    drawing.drawSolidShape(s.radius, s.shape, s.color);
            //    Gl.glPopMatrix();

            //    Gl.glPopMatrix();
            //}
        }

        private void TestDrawHistory()
        {
            drawing.StrokeHistory[0].Clear();
            drawing.StrokeHistory[0].Add(
                new Shape(0.1, 0.1, 0.1, 0, 0.1, 0));
            for (int i = 1; i <= 3; i++)
            {
                drawing.StrokeHistory[0].Add(
                new Shape(Math.Pow(-1,i)*i * 5, i * 5, i * 5, 0, i*5, i, camera.cc.x, camera.cc.y, camera.cc.z));
            }
            drawing.StrokeHistory[0].DrawStroke();
        }

        private void DrawOptimization()
        {
            var gCord = mvc.CalcGlobalCoordinates(-move.X, move.Y, -move.Radius -camera.focallength);
            if (drawing.StrokeHistory.Count == 0)
                drawing.StrokeHistory.Add(new StrokeDisplay());
            drawing.StrokeHistory[drawing.StrokeHistory.Count-1].AddShape(
                        new Shape(
                        gCord[0], gCord[1], gCord[2],
                        drawing.ShapeNumber,
                        move.Trigger / 20.0,
                        drawing.ColorNumber,
                        camera.cc.x,
                        camera.cc.y,
                        camera.cc.z));

            //drawing.History.Add(
            //            new Shape(
            //            move.Radius,
            //            move.Y,
            //            -move.X,
            //            drawing.ShapeNumber,
            //            move.Trigger / 20.0,
            //            drawing.ColorNumber,
            //            camera.cc.x,
            //            camera.cc.y,
            //            camera.cc.z));
        }        

        private double prevX = 0, prevY = 0;
        private bool wasReleased = false;


        private void HandleMoveInputCalibration()
        {
            if (move.IsCircleBtn)
            {
                calibrationStep = 0;
                return;
            }
            if (move.IsMoveButton)
            {
                if (calibrationStep == 1)
                    move.setCalibrationMin();
                if (calibrationStep == 2)
                    move.setCalibrationMax();
                wasReleased = true;
            }
            else if (!move.IsMoveButton && wasReleased)
            {
                calibrationStep++;
                wasReleased = false;
            }
                
        }

        private void HandleMoveInput()
        {
            //camera.cc.a += 0.05;
            //camera.cc.p += 0.05;
            //camera rotation with respect to center of canvas
            if (move.IsMoveButton)
            {
                //if (Math.Abs(move.Ax) > 5)
                //   camera.cc.x += move.Ax / 5;
                //if (Math.Abs(move.Ay) > 5)
                //    camera.cc.x -= move.Ay / 5;
                //if (Math.Abs(move.Az) > 5)
                //    camera.cc.x += move.Az / 5;
                //if (Math.Abs(move.X - prevX) > 5)
                //    camera.cc.y += (move.X - prevX);
                //if (Math.Abs(move.Y - prevY) > 5)
                //    camera.cc.z += (move.Y - prevY);

                
                    if (Math.Abs(move.X - prevX) > 5)
                        camera.cc.a += (move.X - prevX)/100;
                    if (Math.Abs(move.Y - prevY) > 5)
                        camera.cc.p += (move.Y - prevY)/100;

                //if (Math.Abs(move.Mw - prevX) > 0.01)
                //    camera.cc.x += (move.Mw - prevX) * 100;                
                holdBrush = true;
            }
            else
                holdBrush = false;
            if (move.Trigger > 0)
            {
                drawing.UndoHistory.Clear();
                DrawOptimization();
                wasReleased = true;
            }
            else
                if (wasReleased)
                {
                    drawing.StrokeHistory.Add(new StrokeDisplay(0, 0));
                    wasReleased = false;                    
                } 
            if (move.IsSquareBtn)
                drawing.ColorNumber++;
            if (move.IsCircleBtn)
                UndoStroke();
            if (move.IsTriangleBtn)
                RedoStroke();
            if (move.IsSelectBtn)
                CameraHome();
            if (move.IsStartBtn)
                drawing.StrokeHistory.Clear();
            if (move.IsPSButton)
                Glut.glutDestroyWindow(windowId);
            prevX = move.X;
            prevY = move.Y;
        }

        private void UndoStroke()
        {
            if (drawing.StrokeHistory.Count < 2) return;
            var undoneElement = drawing.StrokeHistory[drawing.StrokeHistory.Count - 2];
            drawing.UndoHistory.Add(undoneElement);
            drawing.StrokeHistory.Remove(undoneElement);
        }

        private void RedoStroke()
        {
            if (drawing.UndoHistory.Count == 0) return;
            var redoneElement = drawing.UndoHistory[drawing.UndoHistory.Count - 1];
            drawing.StrokeHistory.RemoveAt(drawing.StrokeHistory.Count - 1);
            drawing.StrokeHistory.Add(redoneElement);
            drawing.StrokeHistory.Add(new StrokeDisplay(0, 0));
            drawing.UndoHistory.Remove(redoneElement);
        }


        private void MakeLightning(bool isOn)
        {
            if (isOn) {
				Gl.glEnable (Gl.GL_LIGHTING);
				Gl.glEnable (Gl.GL_LIGHT0);
				float[] light_pos0 = {0f,1f,0f,0f};
				Gl.glLightfv (Gl.GL_LIGHT0, Gl.GL_POSITION, light_pos0);
			
				Gl.glEnable (Gl.GL_LIGHT1);
				float[] light_pos2 = {0f,-1f,0f,0f};
				Gl.glLightfv (Gl.GL_LIGHT1, Gl.GL_POSITION, light_pos2);
			}
			else
				Gl.glDisable(Gl.GL_LIGHTING);
        }

        private void HandleReshape(int w, int h)
        {
            Gl.glClear (Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
			Gl.glViewport(0,0,w,h);
			camera.screenheight = h;
			camera.screenwidth = w;
            windowHeight = h;
            windowWidth = w;
        }

        private void HandleIdle()
        {
            Glut.glutPostRedisplay();
            Thread.Sleep(25);
        }

        private void HandleVisibility(int visible)
		{
            if (visible == Glut.GLUT_VISIBLE)
                Glut.glutIdleFunc(HandleIdle);
            else
                Glut.glutIdleFunc(null);
		}
        private void CameraHome()
        {
            camera.aperture = 50;
		    camera.focallength = 70;
		    camera.eyesep = camera.focallength / 20;		    

		    camera.vd.x = -1;
		    camera.vd.y = 0;
		    camera.vd.z = 0;

		    camera.vu.x = 0;  
		    camera.vu.y = 1; 
		    camera.vu.z = 0;

            camera.cc = new CameraCalc(camera.focallength);
		}
    }
}
